﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text;

using Kenly.DBFramework;
using Kenly.DBFramework.Reflection;

namespace Kenly.DBFramework.Gateway
{
    /// <summary>
    ///  StoredProcedure object to execute a stored pocedure hosted in database.
    /// </summary>
    public abstract class StoredProcedure<T> : IStoredProcedure where T : StoredProcedure<T>, new()
    {
 
        /// <summary>
        /// 
        /// </summary>
        private T Entity
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        protected ITableCommandExecutor CommandExecutor
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets config from ConfigFactory.
        /// </summary>
        protected GatewayConfig Config
        {
            get
            {
                return ConfigManager.FindConfig(typeof(T));
            }
        }


        /// <summary>
        /// 
        /// </summary>
        public StoredProcedure()
        {
            this.InitializeInstance();

            //GatewayConfig config = ConfigManager.FindConfig(typeof(T));
            //if (config != null)
            //{
            //    this.SetupGateway(config);
            //}

            //if (this.Config == null && !
            //    string.IsNullOrEmpty(_config.ConnectionString))
            //{
            //    this.SetupGateway(_config);
            //}
        }

        private void InitializeInstance()
        {
            this.Entity = (T)this;

            //ProcedureAttribute procedureAttribute = AttributeUtility.GetProcedureAttribute(typeof(T));
            //if (procedureAttribute == null)
            //{
            //    throw new GatewayException(MsgResource.InvalidEntityConfig);
            //}

            this.CommandExecutor = this.CreateTableCommandExecutor();
        }

        /// <summary>
        /// Specifies the DatabaseType and connectionString.
        /// </summary>
        /// <param name="dbType"></param>
        /// <param name="connectionString"></param>
        //[Obsolete("This method is abandoned. Please replace it with the instance method SetupGateway(DatabaseType dbType, string connectionString)")]
        //protected static void InitializeGateway(DatabaseType dbType, string connectionString)
        //{
        //    _config.DatabaseType = dbType;
        //    _config.ConnectionString = connectionString;
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dbType"></param>
        /// <param name="connectionString"></param>
        //protected void SetupGateway(DatabaseType dbType, string connectionString)
        //{
        //    GatewayConfig gatewayConfig = new GatewayConfig();
        //    gatewayConfig.DatabaseType = dbType;
        //    gatewayConfig.ConnectionString = connectionString;

        //    this.SetupGateway(gatewayConfig);
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        //private void SetupGateway(GatewayConfig config)
        //{
        //    if (config == null)
        //    {
        //        throw new ArgumentNullException("config", MsgResource.ConnectionStringMissing);
        //    }
        //    if (string.IsNullOrEmpty(config.ConnectionString))
        //    {
        //        throw new ConnectionException(MsgResource.ConnectionStringMissing);
        //    }

        //    this.Config = config;
        //    this.CommandExecutor = CreateTableCommandExecutor(this.Config);
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        //[Obsolete("This method is abandoned.")]
        //protected void InitializeInstance(T entity)
        //{
        //}


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private ITableCommandExecutor CreateTableCommandExecutor()
        {
            var currentConfig = Config;

            IDatabase database = DatabaseFactory.CreateInstance(currentConfig.DatabaseType);
            database.ConnectionString = currentConfig.ConnectionString;
            database.Command.CommandTimeout = currentConfig.CommandTimeout;
            return database.CreateTableCommandExecutor();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        public void RefreshConfig(GatewayConfig config)
        {
            if (config == null)
            {
                return;
            }

            ConfigManager.Configs[typeof(T)] = config;
            this.CommandExecutor = this.CreateTableCommandExecutor();
        }

        /// <summary>
        /// Begins a database transaction with 60s active time.
        /// </summary>
        /// <returns></returns>
        public bool BeginTransaction()
        {
            if (this.CommandExecutor == null)
            {
                return false;
            }
            ProcedureAttribute procedureAttribute = AttributeUtility.GetProcedureAttribute(typeof(T));
            if (procedureAttribute == null)
            {
                return false;
            }

            return this.CommandExecutor.BeginTransaction() != null;
        }


        /// <summary>
        /// Commits the database transaction.
        /// </summary>
        /// <returns></returns>
        public bool Commit()
        {
            if (this.CommandExecutor == null)
            {
                return false;
            }
            return this.CommandExecutor.Commit();
        }

        /// <summary>
        /// Rolls the database transaction.
        /// </summary>
        /// <returns></returns>
        public bool Rollback()
        {
            if (this.CommandExecutor == null)
            {
                return false;
            }
            return this.CommandExecutor.Rollback();
        }


        #region IStoredProcedure Members


        /// <summary>
        /// Executes a procedure or sql statement ,and returns the number of rows affected.
        /// </summary>
        /// <returns></returns>
        public int ExecuteNonQuery()
        {
            if (this.CommandExecutor == null)
            {
                return 0;
            }
            ProcedureAttribute procedureAttribute;
            Dictionary<string, ParameterAttribute> parameterAttributes;
            string sp;
            List<IDataParameter> dataParameters;
            GetProcedureDetails(out procedureAttribute, out parameterAttributes, out sp, out dataParameters);
            int count = this.CommandExecutor.ExecuteNonQuery(sp, true, dataParameters);
            UpdateOutputProperties(this.Entity, parameterAttributes, dataParameters);
            return count;
        }

        /// <summary>
        /// Executes a procedure or sql statement ,and returns the first column of the first row of the resultset.
        /// </summary>
        /// <returns></returns>
        public object ExecuteScalar()
        {
            if (this.CommandExecutor == null)
            {
                return null;
            }
            ProcedureAttribute procedureAttribute;
            Dictionary<string, ParameterAttribute> parameterAttributes;
            string sp;
            List<IDataParameter> dataParameters;
            GetProcedureDetails(out procedureAttribute, out parameterAttributes, out sp, out dataParameters);
            object obj = this.CommandExecutor.ExecuteScalar(sp, true, dataParameters);
            UpdateOutputProperties(this.Entity, parameterAttributes, dataParameters);
            return obj;
        }

        /// <summary>
        /// Executes a procedure or sql statement ,and builds an IDataReader.
        /// </summary>
        /// <returns></returns>
        public IDataReader ExecuteReader()
        {
            if (this.CommandExecutor == null)
            {
                return null;
            }
            ProcedureAttribute procedureAttribute;
            Dictionary<string, ParameterAttribute> parameterAttributes;
            string sp;
            List<IDataParameter> dataParameters;
            GetProcedureDetails(out procedureAttribute, out parameterAttributes, out sp, out dataParameters);
            IDataReader reader = this.CommandExecutor.ExecuteReader(sp, true, dataParameters);
            UpdateOutputProperties(this.Entity, parameterAttributes, dataParameters);
            return reader;
        }

        /// <summary>
        /// Close the DataReader and the connection to database.
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public void CloseReader(IDataReader reader)
        {
            if (!reader.IsClosed)
            {
                reader.Close();
            }
            if (this.CommandExecutor != null && this.CommandExecutor.Database.ConnectionState != ConnectionState.Closed)
            {
                this.CommandExecutor.Database.Close();
            }
        }


        /// <summary>
        /// Executes a procedure or sql statement ,and returns a DataTable.
        /// </summary>
        /// <returns></returns>
        public DataTable ExecuteDataTable()
        {
            if (this.CommandExecutor == null)
            {
                return new DataTable();
            }
            ProcedureAttribute procedureAttribute;
            Dictionary<string, ParameterAttribute> parameterAttributes;
            string sp;
            List<IDataParameter> dataParameters;
            GetProcedureDetails(out procedureAttribute, out parameterAttributes, out sp, out dataParameters);
            DataTable dt = this.CommandExecutor.ExecuteDataTable(sp, true, dataParameters);
            UpdateOutputProperties(this.Entity, parameterAttributes, dataParameters);
            return dt;
        }

        /// <summary>
        /// Executes a procedure or sql statement ,and returns a DataTable.
        /// </summary>
        /// <typeparam name="TTarget"></typeparam>
        /// <returns></returns>
        public DataTable ExecuteDataTable<TTarget>() where TTarget : new()
        {
            DataTable dt = ExecuteDataTable();
            EntityMapper<TTarget>.AdaptToEntity(dt);
            return dt;
        }

        /// <summary>
        /// Executes a procedure or sql statement ,and returns an array of entities.
        /// </summary>
        /// <typeparam name="TTarget"></typeparam>
        /// <returns></returns>
        public List<TTarget> ExecuteEntity<TTarget>() where TTarget : new()
        {
            DataTable dt = ExecuteDataTable();
            EntityMapper<TTarget>.AdaptToEntity(dt);
            return EntityMapper<TTarget>.ToEntities(dt);
        }


        /// <summary>
        /// Executes a procedure or sql statement ,and returns a DataSet.
        /// </summary>
        /// <returns></returns>
        public DataSet ExecuteDataSet()
        {
            if (this.CommandExecutor == null)
            {
                return new DataSet();
            }
            ProcedureAttribute procedureAttribute;
            Dictionary<string, ParameterAttribute> parameterAttributes;
            string sp;
            List<IDataParameter> dataParameters;
            GetProcedureDetails(out procedureAttribute, out parameterAttributes, out sp, out dataParameters);
            DataSet ds = this.CommandExecutor.ExecuteDataSet(sp, true, dataParameters);
            UpdateOutputProperties(this.Entity, parameterAttributes, dataParameters);
            return ds;
        }

        private void GetProcedureDetails(out ProcedureAttribute procedureAttribute, out Dictionary<string, ParameterAttribute> parameterAttributes,
            out string sp, out List<IDataParameter> dataParameters)
        {
            sp = null;
            procedureAttribute = AttributeUtility.GetProcedureAttribute(typeof(T));
            parameterAttributes = null;
            dataParameters = null;
            if (procedureAttribute == null)
            {
                return;
            }
            parameterAttributes = AttributeUtility.GetParameterAttributes(typeof(T));
            dataParameters = ToDataParameters(this.Entity, this.Config.DatabaseType);
            sp = string.IsNullOrEmpty(procedureAttribute.Name) ? typeof(T).Name : procedureAttribute.Name;
        }



        #endregion


        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="databaseType"></param>
        /// <returns></returns>
        private static List<IDataParameter> ToDataParameters(T entity, DatabaseType databaseType)
        {
            List<IDataParameter> dataParameters = new List<IDataParameter>();
            if (entity == null)
            {
                return dataParameters;
            }

            IParameterBuilder parameterBuilder = ParameterBuilderFactory.CreateInstance(databaseType);

            IDataParameter dataParameter = null;
            foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
            {
                dataParameter = parameterBuilder.CreateProcedureParameter(entity, propertyInfo);
                if (dataParameter != null)
                {
                    dataParameters.Add(dataParameter);
                }
            }
            return dataParameters;
        }



        /// <summary>
        /// Finds the outputParameters and update the ouput properties.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="parameterAttributes"></param>
        /// <param name="dataParameters"></param>
        private static void UpdateOutputProperties(T entity,
            Dictionary<string, ParameterAttribute> parameterAttributes, List<IDataParameter> dataParameters)
        {
            foreach (IDataParameter dataParameter in dataParameters)
            {
                if (dataParameter.Direction == ParameterDirection.Input)
                {
                    continue;
                }
                UpdateOutputProperty(entity, parameterAttributes, dataParameter);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="parameterAttributes"></param>
        /// <param name="outputParameter"></param>
        private static void UpdateOutputProperty(T entity,
            Dictionary<string, ParameterAttribute> parameterAttributes, IDataParameter outputParameter)
        {
            if (outputParameter.Value == null || outputParameter.Value is DBNull)
            {
                return;
            }

            foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
            {
                if (propertyInfo.PropertyType == typeof(OracleCursor))
                {
                    continue;
                }
                if (parameterAttributes[propertyInfo.Name].Name == outputParameter.ParameterName)
                {
                    propertyInfo.SetValue(entity, outputParameter.Value, null);
                    return;
                }
            }
        }





    }
}
